/*
 * Uri.cpp
 *
 *  Created on: 2013年11月16日
 *      Author: zhengchuanjiang
 */

#include "URI.h"
#include <sstream>
#include <string.h>
#include <stdlib.h>


namespace util
{

namespace detail
{

struct SchemePortEntry
{
    const char* scheme;
    uint16_t    port;
};

SchemePortEntry s_portEntry[] =
{
    {"http", 80},
    {"https", 443},
    {"ftp", 21},
    {"rtsp", 554},
    {"rtmp", 1935}
};

size_t findScheme(const std::string& scheme)
{
    size_t count = sizeof(s_portEntry)/sizeof(s_portEntry[0]);
    for (size_t i = 0; i < count; i ++)
    {
        if (scheme.compare(s_portEntry[i].scheme) == 0)
        {
            return i;
        }
    }
    return -1;
}

bool isDefaultPort(const std::string& scheme, uint16_t port)
{
    size_t idx = findScheme(scheme);
    if (idx == (size_t)-1)
    {
        return false;
    }
    return (s_portEntry[idx].port == port);
}

bool getDefaultPort(const std::string& scheme, uint16_t& port)
{
    size_t idx = findScheme(scheme);
    if (idx == (size_t)-1)
    {
        return false;
    }
    port = s_portEntry[idx].port;
    return true;
}


size_t findChar(const std::string& str, size_t start, size_t stop, char ch)
{
    for (size_t i = start; i < stop; i ++)
    {
        if (str[i] == ch)
        {
            return i;
        }
    }
    return -1;
}

bool split(const std::string& str, size_t start, size_t stop, char ch,
        std::string& head, std::string& tail)
{
    bool found = false;
    size_t idx = findChar(str, start, stop, ch);
    if (idx == std::string::npos)
    {
        head = str.substr(start, stop - start);
        tail.clear();
    }
    else
    {
        head = str.substr(start, idx - start);
        tail = str.substr(idx + 1, stop - idx - 1);
        found = true;
    }
    return found;
}

bool split(const std::string& str, size_t start, size_t stop, char ch,
           std::string& head, uint16_t& tail)
{
    std::string strTail;
    bool found = split(str, start, stop, ch, head, strTail);
    if (strTail.empty())
    {
        tail = 0;
    }
    else
    {
        tail = atoi(strTail.c_str());
    }
    return found;
}



} /// end of namespace



Uri::Uri():
    m_port(),
    m_path("/")
{
}

Uri::~Uri()
{
}

Uri::Uri(const std::string& uri):
    m_port()
{
    parse(uri);
}


bool Uri::parse(const std::string& uri)
{
    size_t start = 0;
    size_t pos = uri.find("://", start);
    if (pos == std::string::npos)
    {
        m_scheme.clear();
        pos = 0;
    }
    else
    {
        m_scheme = uri.substr(0, pos);
        start = pos + strlen("://");
    }
    
    pos = uri.find('@', start);
    if (pos == std::string::npos)
    {
        m_userName.clear();
        m_password.clear();
    }
    else
    {
        detail::split(uri, start, pos, ':', m_userName, m_password);

        start = pos + 1;
    }

    pos = uri.find('/', start);
    if (pos == std::string::npos)
    {
        detail::split(uri, start, uri.size(), ':', m_host, m_port);
        if (m_port == 0)
        {
            detail::getDefaultPort(m_scheme, m_port);
        }

        m_path = "/";
        m_query.clear();
        m_fragment.clear();
        return true;
    }

    detail::split(uri, start, pos, ':', m_host, m_port);
    if (m_port == 0)
    {
        detail::getDefaultPort(m_scheme, m_port);
    }

    start = pos;
    pos = uri.find('?', start);
    if (pos == std::string::npos)
    {
        m_query.clear();
        detail::split(uri, start, uri.size(), '#', m_path, m_fragment);
        return true;
    }

    m_path = uri.substr(start, pos - start);
    start = pos + 1;

    detail::split(uri, start, uri.size(), '#', m_query, m_fragment);

    return true;
}

std::string Uri::toString() const
{
    std::ostringstream os;
    os << m_scheme << "://";

    if (m_userName.size() > 0)
    {
        os << m_userName << ":" << m_password << "@";
    }

    os << m_host;

    if (!detail::isDefaultPort(m_scheme, m_port))
    {
        os << ":" << m_port;
    }

    os << m_path;

    if (m_query.size() > 0)
    {
        os << "?" << m_query;
    }

    if (m_fragment.size() > 0)
    {
        os << "#" << m_fragment;
    }
    return os.str();
}

bool Uri::isValid() const
{
    return (!m_scheme.empty() && !m_host.empty() && !m_path.empty());
}

bool Uri::isDefaultPort() const
{
    return detail::isDefaultPort(m_scheme, m_port);
}

bool Uri::isFile() const
{
    return m_scheme == "file";
}

void Uri::clear()
{
    m_scheme.clear();
    m_userName.clear();
    m_path.clear();
    m_host.clear();
    m_port = 0;
    m_path = "/";
    m_query.clear();
    m_fragment.clear();
}

std::string Uri::getUserInfo() const
{
    std::string info(m_userName);
    if (info.size() > 0)
    {
        info += ':';
        info += m_password;
    }
    return info;
}

std::string Uri::getAuthority() const
{
    std::ostringstream os;
    os << m_host;
    if ((m_port != 0) && !isDefaultPort())
    {
        os << ':';
        os << m_port;
    }
    return os.str();
}

bool Uri::isRelative() const
{
    return m_scheme.empty();
}

std::string Uri::getPathEtc() const
{
    std::string pathEtc(m_path);
    if (!m_query.empty())
    {
        pathEtc += '?';
        pathEtc += m_query;
    }
    if (!m_fragment.empty())
    {
        pathEtc += '#';
        pathEtc += m_fragment;
    }
    return pathEtc;
}

std::string Uri::getUrlWithoutUser() const
{
    std::ostringstream os;
    os << m_scheme << "://";

    os << m_host;

    if (!detail::isDefaultPort(m_scheme, m_port))
    {
        os << ":" << m_port;
    }

    os << m_path;

    if (m_query.size() > 0)
    {
        os << "?" << m_query;
    }

    if (m_fragment.size() > 0)
    {
        os << "#" << m_fragment;
    }
    return os.str();
}




} /* namespace net */
